import os.path
from datetime import datetime
import numpy as np
import re
import pandas as pd
from pandas import Timestamp
from dateutil import parser
from datetime import datetime

from dateutil import parser
from datetime import datetime, timedelta
import pandas as pd
# from input import input_info

def datetime_to_striptime(time, format="%Y-%m-%d"):
    """
    将datatime对象转换为指定格式的字符串。
    :param format（str): 输出的字符串格式
    :param time(datatime):要转换的datatime对象
    :return:
    str:转换后的字符串
    """
    if time is None:
        return "None"
    return time.strftime(format)


def convert_to_datetime(time_str):
    """
    将任意格式的时间字符串转换为datetime对象。

    参数:
    time_str (str): 时间字符串，可以是任意格式。

    返回:
    datetime: 转换后的datetime对象。
    """
    if isinstance(time_str, (datetime, pd.Timestamp)):
        return time_str.to_pydatetime() if isinstance(time_str, pd.Timestamp) else time_str
    if time_str is None:
        # _, _, earliest_start_time = input_info()
        # # return datetime(2018, 4, 20)  #注意和ScheduleSYSTEMM一致
        return None

    try:
        # 使用dateutil.parser.parse解析时间字符串
        dt = parser.parse(time_str)
        return dt
    except ValueError as e:
        # 如果解析失败，打印错误信息并返回None
        print(f"无法解析时间字符串：{time_str}，错误信息：{e}")
        return None


class Segment:
    def __init__(self, id, **kwargs):
        self.id = id  # 序号
        self.ship_number = kwargs.get('ship_number')
        self.block_number = kwargs.get('block_number')  # 分段号
        self.shape = kwargs.get('shape')  # 是曲面还是平直分段
        self.type = kwargs.get('type')  # 分段类型（定置或通用）
        self.length = kwargs.get('length', 0)  # 分段长度
        self.width = kwargs.get('width', 0)  # 分段宽度
        self.height = kwargs.get('height')  # 分段高度
        self.weight = kwargs.get('weight', 0)  # 分段重量

        # 获取基准上胎日期并转换为datetime对象
        base_start = kwargs.get('base_start')
        if base_start is None:
            raise ValueError(f"{self.block_number} base_start parameter is required")
        self.base_start = convert_to_datetime(base_start)  # 上胎基准日期

        self.building_period = kwargs.get('building_period', 0)  # 分段建造周期
        self.output_value = kwargs.get('output_value', 0)  # 分段产出值
        # self.area = kwargs.get('area', 0)  # 分段投影面积

        # 获取最早开始时间并转换为datetime对象
        self.earliest_start = convert_to_datetime(kwargs.get('earliest_start'))

        # 获取最晚开始时间并转换为datetime对象
        self.latest_start = convert_to_datetime(kwargs.get('latest_start'))

        # 获取实际开始时间并转换为datetime对象
        self.scheduled_start = convert_to_datetime(kwargs.get('scheduled_start'))

        # 获取实际结束时间并转换为datetime对象
        self.scheduled_finish = convert_to_datetime(kwargs.get('scheduled_finish'))

        self.mold_position = kwargs.get('mold_position')  # 分配的胎位
        self.is_combined = kwargs.get('is_combined', False)  # 是否为组合分段
        self.combined_segments = kwargs.get('combined_segments', 0)  # 组合的分段列表
        self.paired_segment = kwargs.get('paired_segment', 0)  # 成对分段
        self.pre_work = kwargs.get('pre_work', 0)  # 成对分段
        self.placement_type = kwargs.get('placement_type')  # 可放置分段类型（平直或曲面）
        self.topo = kwargs.get('placement_type', [])  # 用于存储在该分段上胎之前上胎的分段
        self.arranged = False
        self.earliest_produced = None
        self.urgency_level = None

    def calculate_base_start(self):
        # 上胎基准日期 = 分段需求节点 - (标准胎位周期 + 各阶段标准工期 + 各阶段缓冲期)
        # 这里简化为需求节点减去建造周期
        self.base_start = self.demand_node - self.building_period

    @property
    def area(self):
        segment_area = self.length * self.width
        return segment_area


    @property
    def delay(self):
        time = (self.scheduled_start - self.latest_start).days
        return time

    @property
    def is_fixed(self):
        if self.block_number == "110" or "111":
            return True
        return False

    # 判断分段是不是上早了

    def is_earlier(self, start_time):
        return start_time < self.earliest_start

    # 判断分段是不是上去晚了

    def is_later(self, start_time):
        return start_time > self.latest_start


class MoldPosition:
    def __init__(self, **kwargs):
        self.id = id  # 胎位编号
        self.first_level_area = kwargs.get('first_level_area')
        self.second_level_area = kwargs.get('second_level_area')
        self.position_ID = kwargs.get('position_ID')
        self.description = kwargs.get('description')
        self.length = kwargs.get('length', 0)  # 胎位有效长度
        self.width = kwargs.get('width', 0)  # 胎位有效宽度
        self.load_capacity = kwargs.get('load_capacity', 0)  # 胎位起重能力
        self.height = kwargs.get('height', 0)  # 胎位高度限制
        self.labor_team = kwargs.get('labor_team')  # 负责的劳务队编号
        self.designated_segment = kwargs.get('designated_segment', 0)  # 指定分段编号（如果有）
        self.placement_type = kwargs.get('placement_type', "通用")  # 可放置分段类型（平直或曲面） #增加类型为通用，2025年4月3日
        self.type = kwargs.get('type')  # 胎位类型（定置或通用）
        # self.area = kwargs.get('area', 0)  # 胎位投影面积
        self.occupied = kwargs.get('occupied', False)  # 胎位是否被占用
        self.current_segment = kwargs.get('current_segment')  # 当前占用的分段
        # self.occupied_intervals = pd.IntervalIndex.from_breaks(
        #     [datetime.datetime.min, datetime.datetime.max])  # 初始化为空闲时间段
        # 获取实际结束时间并转换为datetime对象
        self.last_finish_time = convert_to_datetime(kwargs.get('last_finish_time'))  # 最后计划脱胎日期
        self.record = kwargs.get('record', [])  # 记录已经在该胎位上的分段
        self.left_area = kwargs.get('left_area', {})  # 记录空的空间
        self.value = kwargs.get('left_area', 0)
        self.current_value = 0



    @property
    def area(self):
        return self.length * self.width

    @property
    def is_mobile(self):
        if self.labor_team == "机动":
            return True
        return False

    def is_available(self, start_time):
        # 检查胎位在指定时间段内是否可用
        return not self.occupied or (self.last_finish_time <= start_time)

    @property
    def is_mobile(self):
        return self.labor_team == "机动"

    def is_available(self, start_time, end_time):
        """检查胎位在指定时间段内是否可用"""
        if isinstance(start_time, str):
            start_time = convert_to_datetime(start_time)
        if isinstance(end_time, str):
            end_time = convert_to_datetime(end_time)

        interval_to_check = pd.Interval(start_time, end_time, closed='both')
        for occupied_interval in self.occupied_intervals:
            if interval_to_check.overlaps(occupied_interval):
                return False
        return True

    def add_occupied_interval(self, start_time, end_time):
        """添加被占用的时间段"""
        if isinstance(start_time, str):
            start_time = convert_to_datetime(start_time)
        if isinstance(end_time, str):
            end_time = convert_to_datetime(end_time)

        new_interval = pd.Interval(start_time, end_time, closed='both')
        self.occupied_intervals = self.occupied_intervals.insert(
            len(self.occupied_intervals), new_interval
        )
        self.occupied = True

    def remove_occupied_interval(self, start_time, end_time):
        """移除被占用的时间段"""
        if isinstance(start_time, str):
            start_time = convert_to_datetime(start_time)
        if isinstance(end_time, str):
            end_time = convert_to_datetime(end_time)

        interval_to_remove = pd.Interval(start_time, end_time, closed='both')
        self.occupied_intervals = self.occupied_intervals.difference(
            pd.IntervalIndex([interval_to_remove])
        )

        if self.occupied_intervals.is_empty:
            self.occupied = False
        else:
            self.occupied = True


# 找到所有的成对分段


class CombinedSegment:
    def __init__(self, segments):
        self.segments = segments
        self.id = f"Combined-{segments[0].id}-{segments[1].id}"
        self.type = segments[0].type
        self.is_fixed = segments[0].is_fixed
        self.shape = segments[0].shape
        self.ship_number = segments[0].ship_number
        self.block_number = f"Combined-{segments[0].block_number}-{segments[1].block_number}"
        # self.length = max(seg.length for seg in segments)
        # self.width = max(seg.width for seg in segments)
        # self.height = max(seg.height for seg in segments)
        self.weight = sum(seg.weight for seg in segments)
        self.area = sum(seg.area for seg in segments)
        self.building_period = sum(seg.building_period for seg in segments)
        self.earliest_start = min(seg.earliest_start for seg in segments)
        self.latest_start = min(seg.latest_start for seg in segments)
        self.output_value = sum(seg.output_value for seg in segments)
        self.placement_type = segments[0].placement_type
        # self.demand_node = min(seg.demand_node for seg in segments)
        self.base_start = min(seg.base_start for seg in segments)
        self.scheduled_start = None
        self.scheduled_finish = None
        self.mold_position = None
        self.delay = 0
        self.arranged = False

    @property
    def length(self):
        return max(seg.length for seg in self.segments)

    @property
    def width(self):
        return sum(seg.width for seg in self.segments)

    @property
    def next_length(self):
        return sum(seg.length for seg in self.segments)

    @property
    def next_width(self):
        return max(seg.width for seg in self.segments)

    # 判断分段是不是上早了
    def is_earlier(self, start_time):
        return start_time < self.earliest_start

    # 判断分段是不是上去晚了
    def is_later(self, start_time):
        return start_time > self.latest_start

    def calculate_base_start(self):
        self.base_start = self.demand_node - self.building_period

from collections import defaultdict

class SchedulingSystem:
    def __init__(self, segments, mold_positions):
        self.segments = segments
        self.mold_positions = mold_positions
        self.scheduled_segments = []  # 已排程的分段
        self.abnormal_segments = []  # 排程异常的分段
        self.paired_segments = []
        self.all_combined_segments = []  # 组合分段
        self.standard_time = datetime(2018, 2, 20)  # 系统的标准时间
        self.segemnt_num_dict = defaultdict(int)  # 用于存储分段的哈希表
        # self.standard_time = earliest_time()
        self.monthly_std_segments = self.get_monthly_segments()  # 用于存储每个月的标准分段数量

    def get_monthly_segments(self):
        """
        获取每个月的标准分段数量
        :return:
        """
        # 计算平均值
        if not self.segemnt_num_dict:
            return 0  # 如果字典为空，返回默认值
        min_year_month, max_year_month = self.get_min_max_dates()

        if min_year_month and max_year_month:
            min_year, min_month = min_year_month
            max_year, max_month = max_year_month
        for year in range(min_year, max_year + 1):
            for month in range(min_month, max_month + 1):
                if not (year, month) in self.segemnt_num_dict:
                    self.segemnt_num_dict[(year, month)] = 0

        # 计算平均值
        average_num = sum(self.segemnt_num_dict.values()) / len(self.segemnt_num_dict)

        # 计算方差
        variance = sum((num - average_num) ** 2 for num in self.segemnt_num_dict.values()) / len(
            self.segemnt_num_dict.values())

        # 计算标准差
        std_deviation = variance ** 0.5
        return std_deviation

    def update_monthly_std_segments(self):
        """
        更新每个月的标准分段数量
        """
        self.monthly_std_segments = self.get_monthly_segments()

    def get_min_max_dates(self):
        if not self.segemnt_num_dict:
            return None, None  # 如果字典为空，返回 None

        # 提取所有的年份和月份
        years_months = list(self.segemnt_num_dict.keys())

        # 获取最小和最大的年份与月份
        min_year_month = min(years_months)
        max_year_month = max(years_months)


        return min_year_month, max_year_month

    def schedule_segments(self, rule="responses_area_position and least_area_position"):
        self.initialize_segments()
        self.initialize_mold_positions()
        self.sort_segments()
        self.paired_segment_allocation()
        self.combined_segment_allocation(rule)
        self.normal_segment_allocation()
        # self.abnormal_allocation()

    def initialize_mold_positions(self):
        for position in self.mold_positions:
            if position.last_finish_time is None:
                position.last_finish_time = self.standard_time

    def initialize_segments(self):
        # 初始化分段的上胎基准日期
        # for segment in self.segments:
        #     if isinstance(segment.base_start, str):
        #         segment.base_start = datetime.strptime(segment.base_start, '%Y-%m-%d')
        #     if isinstance(segment.earliest_start, str):
        #         segment.earliest_start = datetime.strptime(segment.earliest_start, '%Y-%m-%d')
        #     if isinstance(segment.latest_start, str):
        #         segment.latest_start = datetime.strptime(segment.latest_start, '%Y-%m-%d')

        # 处理组合分段
        for segment in self.segments:
            combined = []
            if segment.combined_segments:
                combined.append(segment)
                for seg in self.segments:
                    if segment.combined_segments == seg.block_number and segment.ship_number == seg.ship_number:
                        combined.append(seg)
                if len(combined) == 2:
                    combined_segment = CombinedSegment(combined)
                    # combined_segment.calculate_base_start()
                    self.all_combined_segments.append(combined_segment)
                    # 为另一个组合分段设置组合分段
                    for seg in combined:
                        for another_seg in combined:
                            if seg != another_seg:
                                seg.combined_segments = another_seg

    def find_all_paired_segments(self):
        """
        在一个集合中找到所有成对的分段，使用首尾数字作为哈希表的键
        :param segments: 分段集合
        :return: 成对分段的列表，每个元素是一个元组，包含两个成对的分段
        """
        # paired_segments = []
        segment_map = {}  # 用于存储分段的哈希表

        # 定义中间数字的成对规则
        pair_rules = {
            '2': ['3'],
            '3': ['2'],
            '4': ['5'],
            '5': ['4'],
            '6': ['7'],
            '7': ['6'],
            '8': ['9'],
            '9': ['8']
        }
        for segment in self.segments:
            # 获取分段的工程号和分段号
            ship_number = segment.ship_number
            block_number = segment.block_number

            # # 确保分段号是3位数字
            # if len(block_number) != 3:
            #     continue

            # 提取首尾数字和中间数字
            first = block_number//100
            middle = block_number//10 - first * 10
            last = block_number - middle * 10 - first * 100

            # 检查中间数字是否有对应的成对数字
            if 1 < middle < 9:
                paired_middle = middle + 1
                # 构造可能的成对分段号
                paired_block_number = first*100 + paired_middle*10 + last

                # 检查是否存在成对的分段
                if (ship_number, paired_block_number) in segment_map.keys():
                    paired_segment = segment_map[(ship_number, paired_block_number)][0]
                    # 确定哪个分段更早开始
                    if segment.base_start > paired_segment.base_start:
                        paired_segment.paired_segment = segment
                        segment.pre_work = paired_segment
                        self.paired_segments.append((paired_segment, segment))  # 先放开始早的
                    else:
                        segment.paired_segment = paired_segment
                        paired_segment.pre_work = segment
                        self.paired_segments.append((segment, paired_segment))
                    # 将成对分段添加到结果列表

            # 将当前分段添加到哈希表中，以便后续查找
            segment_map_key = (ship_number, block_number)
            if segment_map_key not in segment_map:
                segment_map[segment_map_key] = []
            segment_map[segment_map_key].append(segment)
        return

    def sort_segments(self):
        # 根据上胎基准日期对分段进行排序
        self.segments.sort(key=lambda x: x.base_start)
        # self.combined_segments.sort(key=lambda x: x.base_start)

    def find_start_time(self, segment, mold_position):
        if mold_position.last_finish_time is not None:
            start_time = max(segment.base_start, mold_position.last_finish_time + timedelta(1))
        else:
            start_time = max(segment.base_start, self.standard_time)
        return start_time

    def match(self, start_time, segment, mold_position):
        # if segment.is_earlier(start_time) or segment.is_later(start_time):   #暂时不用准时
        #     return False
        if not self.is_segment_mold_match(segment, mold_position):
            return False
        return True

    def is_same_region(self, mold_position, pre_mold_position):
        if pre_mold_position.second_level_area != mold_position.second_level_area:
            return False
        if pre_mold_position.labor_team != mold_position.labor_team:
            return False
        return True

    def paired_segment_allocation(self):
        # 优先调度成对分段
        # todo 优化胎位的尺寸使之不会过大
        self.find_all_paired_segments()
        sort_with_region = sorted(self.mold_positions, key=lambda x: (x.description, x.area))
        for segment, paired_segment in self.paired_segments:
            pre_assigned, next_assigned = False, False
            for mold_position in sort_with_region:
                print("找下一个胎位")
                if not segment.pre_work:
                    pre_segment = segment
                    next_segment = paired_segment
                else:
                    pre_segment = paired_segment
                    next_segment = segment
                if not pre_assigned:
                    pre_start_time = self.find_start_time(pre_segment, mold_position)
                    if self.match(pre_start_time, pre_segment, mold_position):
                        pre_finish_time = pre_start_time + timedelta(days=pre_segment.building_period)
                        pre_mold_position = mold_position
                        pre_assigned = True
                        print("有一个成对分段找到胎位了")
                        continue  # 避免同一个分段继续进行安排
                if pre_assigned and not next_assigned:
                    if self.is_same_region(mold_position, pre_mold_position):
                        start_time = self.find_start_time(next_segment, mold_position)
                        if start_time - pre_start_time > timedelta(5):
                            print("延迟")
                            print(start_time - pre_start_time )
                            continue
                        print("即将判断合不合适")
                        if self.match(start_time, next_segment, mold_position):
                            print("判断合适")
                            finish_time = start_time + timedelta(days=next_segment.building_period)
                            next_assigned = True
                            print("又有一个成对分段找到胎位了")
            if pre_assigned and next_assigned:
                print("成对分段找到胎位了")
                self.assign_segment_to_mold(pre_segment, mold_position, pre_start_time, pre_finish_time)
                self.assign_segment_to_mold(next_segment, mold_position, start_time, finish_time)
                break
            # 经过一轮没有找到，实际上还是可以再试试的
            if not pre_assigned or not next_assigned:
                print("成对分段没有找到胎位")
                self.abnormal_segments.append(pre_segment)
                self.abnormal_segments.append(next_segment)
    def sorted_position_with(self, rule):
        if rule == "responses_earliest_position":
            return sorted(self.mold_positions, key=lambda x: (x.last_finish_time or self.standard_time))
        if rule == "least_area_position":
            return sorted(self.mold_positions, key=lambda x: (x.area))
        if rule == "responses_area_position and least_area_position":
            return sorted(self.mold_positions, key=lambda x: (x.last_finish_time or self.standard_time, x.area))

    def normal_segment_allocation(self, rule):
        # 调度普通分段
        sort_with_time_position = sorted(self.mold_positions, key=lambda x: (x.last_finish_time or self.standard_time, x.area))
        for segment in self.segments:
            assigned = False
            sort_position = self.sorted_position_with(rule)
            for mold_position in sort_position:
                if segment.arranged:
                    assigned = True
                    # print("分段已经安排好了")
                    break
                # print("分段没安排好了")
                if not np.isnan(segment.combined_segments) and not np.isnan(segment.is_combined): # 注意NAN的判断方式
                    assigned = True
                    break
                # print("分段不是组合分段")
                if segment.pre_work or segment.paired_segment:
                    assigned = True
                    # print("分段是成对分段")
                    break
                # print("分段不是成对分段")
                if mold_position.last_finish_time:
                    start_time = max(segment.base_start, mold_position.last_finish_time + timedelta(1))
                else:
                    start_time = segment.base_start
                if segment.is_earlier(start_time) or segment.is_later(start_time):
                    continue
                if not self.is_segment_mold_match(segment, mold_position):
                    continue

                finish_time = start_time + timedelta(days=segment.building_period)

                self.assign_segment_to_mold(segment, mold_position, start_time, finish_time)
                assigned = True
                break

            if not assigned:
                self.abnormal_segments.append(segment)

    def combined_segment_allocation(self):
        # 调度组合分段
        for combined_segment in self.all_combined_segments:
            assigned = False
            sort_with_time_position = sorted(self.mold_positions, key=lambda x: (x.last_finish_time or self.standard_time, x.area))
            for mold_position in sort_with_time_position:
                if combined_segment.arranged:
                    continue
                if mold_position.last_finish_time:
                    start_time = max(combined_segment.base_start, mold_position.last_finish_time + timedelta(1))
                else:
                    start_time = combined_segment.base_start

                if combined_segment.is_earlier(start_time) or combined_segment.is_later(start_time):
                    continue
                # for i in range(2):
                    # if i == 1:  #尝试第二种组合方式
                    #     combined_segment.length = combined_segment.next_length
                    #     combined_segment.width = combined_segment.next_width
                if not self.is_segment_mold_match(combined_segment, mold_position):
                    continue
                finish_time = start_time + timedelta(days=combined_segment.building_period)
                self.assign_segment_to_mold(combined_segment, mold_position, start_time, finish_time)
                assigned = True
                break
            if not assigned:
                self.abnormal_segments.append(combined_segment)

    def abnormal_allocation(self):
        print("开始不正常分段的安排")
        sort_with_time_position = sorted(self.mold_positions, key=lambda x: x.last_finish_time or self.standard_time)
        for segment in self.abnormal_segments:
            assigned = False
            for mold_position in sort_with_time_position:
                if segment.arranged:
                    print(f"{segment.id}分段已经安排好了")
                    break
                print(f"{segment.id}分段没安排好了")
                if not np.isnan(segment.combined_segments) and not np.isnan(segment.is_combined):  # 注意NAN的判断方式
                    break
                print(f"{segment.id}分段不是组合分段")
                if segment.pre_work or segment.paired_segment:
                    print(f"{segment.id}分段是成对分段")
                    break
                print(f"{segment.id}分段不是成对分段")
                if mold_position.last_finish_time:
                    start_time = max(segment.base_start, mold_position.last_finish_time + timedelta(1))
                else:
                    start_time = segment.base_start
                if segment.is_earlier(start_time) or segment.is_later(start_time):
                    print(f"{segment.id}时间太早或太晚了")
                    continue
                if not self.is_segment_mold_match(segment, mold_position):
                    print(f"{segment.id}胎位尺寸不匹配")
                    continue

                finish_time = start_time + timedelta(days=segment.building_period)

                self.assign_segment_to_mold(segment, mold_position, start_time, finish_time)
                assigned = True
                break

            if not assigned:
                self.abnormal_segments.append(segment)

    def is_match(self, segment, mold_position):
        if mold_position.last_finish_time:
            start_time = max(segment.base_start, mold_position.last_finish_time + timedelta(1))
        else:
            start_time = segment.base_start
        if segment.is_earlier(start_time) or segment.is_later(start_time):
            print(f"{segment.id}时间太早或太晚了")
            return False
        if not self.is_segment_mold_match(segment, mold_position):
            print(f"{segment.id}胎位尺寸不匹配")
            return False
        finish_time = start_time + timedelta(days=segment.building_period)
        self.assign_segment_to_mold(segment, mold_position, start_time, finish_time)
        return True

    # 暂时考虑分段可以旋转90度，增加放置的可行性
    def is_size_suitable(self, segment, mold_position):
        if segment.length <= mold_position.length and segment.width <= mold_position.width:
            return True
        if segment.length <= mold_position.width and segment.width <= mold_position.length:
            return True
        return False

    def is_segment_mold_match(self, segment, mold_position):
        # 优先考虑尺寸是否合适的
        if not self.is_size_suitable(segment, mold_position):
            print(f"{segment.id}胎位尺寸不匹配")
            return False
        if segment.weight > mold_position.load_capacity:
            print(f"{segment.id}胎位负载不足")
            return False
        if not np.isnan(mold_position.designated_segment) and segment.block_number == mold_position.designated_segment:
            print("定制分段匹配成功")
            return True
        if not np.isnan(mold_position.designated_segment) and segment.block_number != mold_position.designated_segment:
            print("定制分段匹配失败")
            return False
        if mold_position.type == "定置" and mold_position.placement_type == "曲" and segment.shape != "曲":
            print("曲线分段不匹配")
            return False
        if mold_position.type == "通用" and mold_position.placement_type == "平" and segment.shape != "平":
            print("平直分段不匹配")
            return False
        if mold_position.type == "通用" and mold_position.placement_type == "曲" and segment.shape != "曲":
            print("曲线分段不匹配")
            return False
        return True

    def assign_segment_to_mold(self, segment, mold_position, start_time, finish_time):
        # 分配分段到胎位
        if isinstance(segment, CombinedSegment):
            segment.scheduled_start = start_time
            segment.scheduled_finish = finish_time
            segment.mold_position = mold_position
            mold_position.occupied = True
            mold_position.current_segment = segment
            mold_position.last_finish_time = finish_time
            self.scheduled_segments.append(segment)
            for seg in segment.segments:
                seg.scheduled_start = start_time
                seg.scheduled_finish = finish_time
                seg.mold_position = mold_position
                seg.arranged = True
        else:
            segment.scheduled_start = start_time
            segment.scheduled_finish = finish_time
            segment.mold_position = mold_position
            mold_position.occupied = True
            mold_position.current_segment = segment
            mold_position.last_finish_time = finish_time
            self.scheduled_segments.append(segment)
            segment.arranged = True

    def display_results(self):
        print("排程结果：")
        for segment in self.scheduled_segments:
            scheduled_start_str = datetime_to_striptime(segment.scheduled_start)
            scheduled_finish_str = datetime_to_striptime(segment.scheduled_finish)
            # scheduled_start_str = segment.scheduled_start
            # scheduled_finish_str = segment.scheduled_finish
            if isinstance(segment, CombinedSegment):
                print(f"组合分段 {segment.id} 被分配到胎位 {segment.mold_position.description}")
                print(f"上胎时间: {scheduled_start_str}, 下胎时间: {scheduled_finish_str}")
                for seg in segment.segments:
                    print(f"  包含分段 {seg.id}")
                    if seg.delay > 0:
                        print(f"  延期 {seg.delay} 天")
                    else:
                        print(f"  在胎位红线前完工")
            else:

                print(f"分段 {segment.id} 被分配到胎位 {segment.mold_position.description}")
                print(f"上胎时间: {scheduled_start_str}, 下胎时间: {scheduled_finish_str}")
                if segment.delay > 0:
                    print(f"延期 {segment.delay} 天")
                else:
                    print("在胎位红线前完工")
            print("----------------------")

        if self.abnormal_segments:
            print("异常分段：")
            for segment in self.abnormal_segments:
                print(f"分段 {segment.id} 无法找到合适的胎位")

    def export_to_excel(self,name):
        # 创建DataFrame
        data = []
        max_makespan = 0
        max_finish_time = self.standard_time
        min_start_time = self.standard_time
        for segment in self.scheduled_segments:
            row = {
                "胎位描述": segment.mold_position.description,
                "胎位编号": segment.mold_position.position_ID,
                "胎位长": segment.mold_position.length,
                "胎位宽": segment.mold_position.width,
                "劳务队": segment.mold_position.labor_team,
                "船号": segment.ship_number,
                "分段序号": segment.id,
                "分段号": segment.block_number,
                "分段长": segment.length,
                "分段宽": segment.width,
                "分段加工时间": segment.building_period,
                "分段产值": segment.output_value,
                "计划开始时间": segment.scheduled_start,
                "计划结束时间": segment.scheduled_finish,
                "最晚完工时间": segment.latest_start + timedelta(segment.building_period),
                "延期天数": segment.delay,
                "提前天数": segment.base_start - segment.scheduled_start,
                "正常提前天数": segment.base_start - segment.earliest_start
                          }

            max_finish_time = max(max_finish_time, segment.scheduled_finish)
            min_start_time = min(min_start_time, segment.scheduled_start)
            max_makespan = max(max_makespan, (segment.scheduled_finish - min_start_time).days)
            data.append(row)

        df = pd.DataFrame(data)

        # 定义Excel文件路径
        excel_path = f'数据导出/scheduling_results{name}.xlsx'
        dir_path = os.path.dirname(excel_path)
        if not os.path.exists(dir_path):
            os.makedirs(dir_path)

        # 写入Excel文件
        with pd.ExcelWriter(excel_path, engine='openpyxl') as writer:
            df.to_excel(writer, sheet_name='Scheduled Segments', index=False)

        print(f"排程结果已导出到 {excel_path}")
        return max_makespan, max_finish_time  #增加best_makespan和best_finish_time


class StackSchedulingSystem:
    def __init__(self, segments, mold_positions):
        self.segments = segments
        self.mold_positions = mold_positions
        self.scheduled_segments = []  # 已排程的分段
        self.abnormal_segments = []  # 排程异常的分段
        self.combined_segments = []  # 组合分段
        self.standard_time = datetime(2018, 4, 20)  # 系统的标准时间
        # self.mold_stack = []

    def initialize_mold_positions(self):
        for position in self.mold_positions:

            if position.last_finish_time is None:
                position.last_finish_time = self.standard_time

    def initialize_segments(self):
        # 初始化分段的上胎基准日期
        for segment in self.segments:
            if isinstance(segment.base_start, str):
                segment.base_start = datetime.strptime(segment.base_start, '%Y-%m-%d')
            if isinstance(segment.earliest_start, str):
                segment.earliest_start = datetime.strptime(segment.earliest_start, '%Y-%m-%d')
            if isinstance(segment.latest_start, str):
                segment.latest_start = datetime.strptime(segment.latest_start, '%Y-%m-%d')

        # 处理组合分段
        combined = []
        for segment in self.segments:
            if segment.is_combined and segment not in combined:
                combined_group = [seg for seg in self.segments if
                                  seg.is_combined and seg.combined_segments == segment.combined_segments]
                combined.append(combined_group)
                combined_segment = CombinedSegment(combined_group)
                combined_segment.calculate_base_start()
                self.combined_segments.append(combined_segment)
                for seg in combined_group:
                    seg.combined_segments = combined_segment

    def sort_segments(self):
        # 根据上胎基准日期对分段进行排序
        self.segments.sort(key=lambda x: x.base_start)
        self.combined_segments.sort(key=lambda x: x.base_start)

    def schedule_segments(self):
        # self.initialize_segments()
        self.initialize_mold_positions()
        self.sort_segments()

        # 调度组合分段
        for combined_segment in self.combined_segments:
            assigned = False

            for mold_position in self.mold_positions:
                start_time = max(combined_segment.base_start, mold_position.last_finish_time + self.standard_time)
                if start_time > combined_segment.latest_start or finish_time > combined_segment.demand_node:
                    continue
                if not self.is_segment_mold_match(combined_segment, mold_position):
                    continue

                finish_time = start_time + combined_segment.building_period

                if start_time > combined_segment.latest_start or finish_time > combined_segment.demand_node:
                    continue

                self.assign_segment_to_mold(combined_segment, mold_position, start_time, finish_time)
                assigned = True
                break

            if not assigned:
                self.abnormal_segments.extend(combined_segment.segments)

        # 调度普通分段和成对分段
        for mold_position in self.mold_positions:
            for segment in self.segments:
                # assigned = False
                if segment.arranged:
                    continue
                if segment.is_combined or segment.pair_segment:
                    continue
                # for mold_position in self.mold_positions:
                if mold_position.last_finish_time:
                    start_time = max(segment.base_start, mold_position.last_finish_time + timedelta(1))
                else:
                    start_time = segment.base_start
                if segment.is_earlier(start_time) or segment.is_later(start_time):
                    continue
                if not self.is_segment_mold_match(segment, mold_position):
                    continue

                finish_time = start_time + timedelta(days=segment.building_period)

                self.assign_segment_to_mold(segment, mold_position, start_time, finish_time)
                assigned = True
                break

            if not assigned:
                self.abnormal_segments.append(segment)

    # 暂时考虑分段可以旋转90度，增加放置的可行性
    def is_size_suitable(self, segment, mold_position):
        if segment.length <= mold_position.length and segment.width <= mold_position.width:
            return True
        if segment.length <= mold_position.width and segment.width <= mold_position.length:
            return True
        return False

    def is_segment_mold_match(self, segment, mold_position):
        # 优先考虑尺寸是否合适的
        if not self.is_size_suitable(segment, mold_position):
            return False
        if segment.weight > mold_position.load_capacity:
            return False
        if not np.isnan(mold_position.designated_segment) and segment.block_number != mold_position.designated_segment:
            return False
        if mold_position.type == "定置" and mold_position.placement_type == "曲" and segment.shape != "曲":
            return False
        if mold_position.type == "通用" and mold_position.placement_type == "平" and segment.shape != "平":
            return False
        if mold_position.type == "通用" and mold_position.placement_type == "曲" and segment.shape != "曲":
            return False
        # if isinstance(segment, CombinedSegment):
        #     if any(seg.is_fixed for seg in segment.segments) and mold_position.is_mobile:
        #         return False
        # if not np.isnan(mold_position.designated_segment):
        #     if isinstance(segment, CombinedSegment):
        #         if mold_position.designated_segment not in [seg.id for seg in segment.segments]:
        #             return False
        #     else:
        #         if mold_position.designated_segment != segment.id:
        #             return False
        return True

    def assign_segment_to_mold(self, segment, mold_position, start_time, finish_time):
        # 分配分段到胎位
        segment.arranged = True

        if isinstance(segment, CombinedSegment):
            segment.scheduled_start = start_time
            segment.scheduled_finish = finish_time
            segment.mold_position = mold_position
            mold_position.occupied = True
            mold_position.current_segment = segment
            mold_position.last_finish_time = finish_time
            self.scheduled_segments.append(segment)
            for seg in segment.segments:
                seg.scheduled_start = start_time
                seg.scheduled_finish = finish_time
                seg.mold_position = mold_position
        else:
            segment.scheduled_start = start_time
            segment.scheduled_finish = finish_time
            segment.mold_position = mold_position
            mold_position.occupied = True
            mold_position.current_segment = segment
            mold_position.last_finish_time = finish_time
            self.scheduled_segments.append(segment)

    def display_results(self):
        print("排程结果：")
        for segment in self.scheduled_segments:
            if segment.mold_position:
                scheduled_start_str = datetime_to_striptime(segment.scheduled_start)
                scheduled_finish_str = datetime_to_striptime(segment.scheduled_finish)
                # scheduled_start_str = segment.scheduled_start
                # scheduled_finish_str = segment.scheduled_finish
                if isinstance(segment, CombinedSegment):
                    print(f"组合分段 {segment.id} 被分配到胎位 {segment.mold_position.id}")
                    print(f"上胎时间: {scheduled_start_str}, 下胎时间: {scheduled_finish_str}")
                    for seg in segment.segments:
                        print(f"  包含分段 {seg.id}")
                        if seg.delay > 0:
                            print(f"  延期 {seg.delay} 天")
                        else:
                            print(f"  在胎位红线前完工")
                else:

                    print(f"分段 {segment.id} 被分配到胎位 {segment.mold_position.description}")
                    print(f"上胎时间: {scheduled_start_str}, 下胎时间: {scheduled_finish_str}")
                    if segment.delay > 0:
                        print(f"延期 {segment.delay} 天")
                    else:
                        print("在胎位红线前完工")
                print("----------------------")

        if self.abnormal_segments:
            print("异常分段：")
            for segment in self.abnormal_segments:
                print(f"分段 {segment.id} 无法找到合适的胎位")

    def export_to_excel(self):
        # 创建DataFrame
        data = []
        for segment in self.scheduled_segments:
            if segment.mold_position:
                row = {
                    "胎位描述": segment.mold_position.description,
                    "胎位编号": segment.mold_position.position_ID,
                    "胎位长": segment.mold_position.length,
                    "胎位宽": segment.mold_position.width,
                    "劳务队": segment.mold_position.labor_team,
                    "船号": segment.ship_number,
                    "分段": segment.id,
                    "分段长": segment.length,
                    "分段宽": segment.width,
                    "计划开始时间": segment.scheduled_start,
                    "计划结束时间": segment.scheduled_finish,
                    "最晚完工时间": segment.latest_start + timedelta(segment.building_period),
                    # "分段需求点": segment.demand_node
                }
                data.append(row)
        df = pd.DataFrame(data)

        # 定义Excel文件路径
        excel_path = '数据导出/stack_scheduling_results.xlsx'
        dir_path = os.path.dirname(excel_path)
        if not os.path.exists(dir_path):
            os.makedirs(dir_path)

        # 写入Excel文件
        with pd.ExcelWriter(excel_path, engine='openpyxl') as writer:
            df.to_excel(writer, sheet_name='Scheduled Segments', index=False)

        print(f"排程结果已导出到 {excel_path}")


# class SchedulingSystem:
#     def __init__(self, segments, mold_positions):
#         self.segments = segments
#         self.mold_positions = mold_positions
#         self.scheduled_segments = []  # 已排程的分段
#         self.abnormal_segments = []  # 排程异常的分段
#         self.combined_segments = []  # 组合分段
#         self.standard_time = pd.Timestamp('2018-04-20') #系统的标准时间
#
#     def initialize_mold_positions(self):
#         for position in self.mold_positions:
#             if isinstance(position.last_finish_time, Timestamp):
#                 position.last_finish_time = int((position.last_finish_time - self.standard_time).days)
#
#
#     def initialize_segments(self):
#         # 初始化分段的上胎基准日期
#         for segment in self.segments:
#             segment.base_start = (segment.base_start - self.standard_time).days
#
#         # 处理组合分段
#         combined = []
#         for segment in self.segments:
#             if segment.is_combined and segment not in combined:
#                 combined_group = [seg for seg in self.segments if
#                                   seg.is_combined and seg.combined_segments == segment.combined_segments]
#                 combined.append(combined_group)
#                 combined_segment = CombinedSegment(combined_group)
#                 combined_segment.calculate_base_start()
#                 self.combined_segments.append(combined_segment)
#                 for seg in combined_group:
#                     seg.combined_segments = combined_segment
#
#     def sort_segments(self):
#         # 根据上胎基准日期对分段进行排序
#         self.segments.sort(key=lambda x: x.base_start)
#         self.combined_segments.sort(key=lambda x: x.base_start)
#
#     def schedule_segments(self):
#         self.initialize_segments()
#         self.initialize_mold_positions()
#         for segment in self.segments:
#             if isinstance(segment.base_start, str):
#                 segment.base_start = datetime.strptime(segment.base_start, '%Y-%m-%d')
#         # for segment in self.segments:
#         #     if not isinstance(segment.base_start, datetime):
#         #         print(f"base_start 属性不是 datetime.datetime 类型: {segment.base_start}")
#         self.sort_segments()
#
#         # 调度组合分段
#         for combined_segment in self.combined_segments:
#             assigned = False
#             for mold_position in self.mold_positions:
#                 if not self.is_segment_mold_match(combined_segment, mold_position):
#                     continue
#
#                 start_time = max(combined_segment.base_start, mold_position.last_finish_time)
#                 finish_time = start_time + combined_segment.building_period
#
#                 if start_time > combined_segment.latest_start or finish_time > combined_segment.demand_node:
#                     continue
#
#                 self.assign_segment_to_mold(combined_segment, mold_position, start_time, finish_time)
#                 assigned = True
#                 break
#
#             if not assigned:
#                 self.abnormal_segments.extend(combined_segment.segments)
#
#         # 调度普通分段和成对分段
#         for segment in self.segments:
#             if segment.is_combined or segment.pair_segment:
#                 continue
#
#             assigned = False
#             for mold_position in self.mold_positions:
#                 if not self.is_segment_mold_match(segment, mold_position):
#                     continue
#                 print(f"segment.base_start: {segment.base_start}, type: {type(segment.base_start)}")
#                 print(
#                     f"mold_position.last_finish_time: {mold_position.last_finish_time}, type: {type(mold_position.last_finish_time)}")
#                 print(segment.base_start)
#                 print(mold_position.last_finish_time)
#                 start_time = max(segment.base_start, mold_position.last_finish_time+1)
#
#                 finish_time = start_time + segment.building_period
#
#                 if start_time <= (segment.latest_start-self.standard_time).days and start_time >= (segment.earliest_start-self.standard_time).days:
#                     continue
#
#                 self.assign_segment_to_mold(segment, mold_position, start_time, finish_time)
#                 assigned = True
#                 break
#
#             if not assigned:
#                 self.abnormal_segments.append(segment)
#
#         # # 调度成对分段
#         # for segment in self.segments:
#         #     if segment.pair_segment is None:
#         #         continue
#         #
#         #     pair_segment = segment.pair_segment
#         #     if pair_segment.mold_position is not None:
#         #         # 尝试将当前分段分配到与成对分段相同的劳务队的胎位
#         #         for mold_position in self.mold_positions:
#         #             if mold_position.labor_team != pair_segment.mold_position.labor_team:
#         #                 continue
#         #
#         #             if not self.is_segment_mold_match(segment, mold_position):
#         #                 continue
#         #
#         #             start_time = max(segment.base_start, mold_position.last_finish_time)
#         #             finish_time = start_time + segment.building_period
#         #
#         #             if start_time > segment.latest_start or finish_time > segment.demand_node:
#         #                 continue
#         #
#         #             if abs(start_time - pair_segment.scheduled_start) > 5:
#         #                 continue
#         #
#         #             self.assign_segment_to_mold(segment, mold_position, start_time, finish_time)
#         #             assigned = True
#         #             break
#
#             # if not assigned:
#             #     self.abnormal_segments.append(segment)
#
#     def is_segment_mold_match(self, segment, mold_position):
#         #
#         if not np.isnan(mold_position.designated_segment) and segment.block_number != mold_position.designated_segment:
#             return False
#         if mold_position.type == "定置" and mold_position.placement_type == "曲" and segment.shape != "曲":
#             return False
#         if mold_position.type == "通用" and mold_position.placement_type == "平" and segment.shape != "平":
#             return False
#         if mold_position.type == "通用" and mold_position.placement_type == "曲" and segment.shape != "曲":
#             return False
#         if segment.length > mold_position.length or segment.width > mold_position.width:
#             return False
#         if segment.weight > mold_position.load_capacity:
#             return False
#         if segment.is_fixed and mold_position.is_mobile:
#             return False
#         if isinstance(segment, CombinedSegment):
#             if any(seg.is_fixed for seg in segment.segments) and mold_position.is_mobile:
#                 return False
#         if not np.isnan(mold_position.designated_segment):
#             if isinstance(segment, CombinedSegment):
#                 if mold_position.designated_segment not in [seg.id for seg in segment.segments]:
#                     return False
#             else:
#                 if mold_position.designated_segment != segment.id:
#                     return False
#         return True
#
#     def assign_segment_to_mold(self, segment, mold_position, start_time, finish_time):
#         # 分配分段到胎位
#         if isinstance(segment, CombinedSegment):
#             segment.scheduled_start = start_time
#             segment.scheduled_finish = finish_time
#             segment.mold_position = mold_position
#             mold_position.occupied = True
#             mold_position.current_segment = segment
#             mold_position.last_finish_time = finish_time
#             self.scheduled_segments.append(segment)
#             for seg in segment.segments:
#                 seg.scheduled_start = start_time
#                 seg.scheduled_finish = finish_time
#                 seg.mold_position = mold_position
#                 # if finish_time > seg.demand_node:
#                 #     seg.delay = self.delay
#                 # else:
#                 #     seg.delay = self.delay
#         else:
#             segment.scheduled_start = start_time
#             segment.scheduled_finish = finish_time
#             segment.mold_position = mold_position
#             mold_position.occupied = True
#             mold_position.current_segment = segment
#             mold_position.last_finish_time = finish_time
#             self.scheduled_segments.append(segment)
#             # if finish_time > segment.demand_node:
#             #     segment.delay = finish_time - segment.demand_node
#             # else:
#             #     segment.delay = 0
#
#     def display_results(self):
#         print("排程结果：")
#         for segment in self.scheduled_segments:
#             if isinstance(segment, CombinedSegment):
#                 print(f"组合分段 {segment.id} 被分配到胎位 {segment.mold_position.id}")
#                 print(f"上胎时间: {segment.scheduled_start}, 下胎时间: {segment.scheduled_finish}")
#                 for seg in segment.segments:
#                     print(f"  包含分段 {seg.id}")
#                     if seg.delay > 0:
#                         print(f"  延期 {seg.delay} 天")
#                     else:
#                         print(f"  在胎位红线前完工")
#             else:
#                 print(f"分段 {segment.id} 被分配到胎位 {segment.mold_position.id}")
#                 print(f"上胎时间: {segment.scheduled_start}, 下胎时间: {segment.scheduled_finish}")
#                 if segment.delay > 0:
#                     print(f"延期 {segment.delay} 天")
#                 else:
#                     print("在胎位红线前完工")
#             print("----------------------")
#
#         if self.abnormal_segments:
#             print("异常分段：")
#             for segment in self.abnormal_segments:
#                 print(f"分段 {segment.id} 无法找到合适的胎位")


# 示例用法
if __name__ == "__main__":
    # 创建分段对象
    segments = [
        Segment(
            id=1,
            type="定置",
            is_fixed=True,
            length=10,
            width=5,
            height=3,
            weight=1000,
            area=50,
            building_period=30,
            earliest_start=0,
            latest_start=100,
            output_value=5000,
            placement_type="曲面",
            demand_node=100
        ),
        Segment(
            id=2,
            type="通用",
            is_fixed=False,
            length=8,
            width=4,
            height=2,
            weight=800,
            area=32,
            building_period=25,
            earliest_start=0,
            latest_start=90,
            output_value=4000,
            placement_type="平直",
            demand_node=90
        ),
        Segment(
            id=3,
            type="定置",
            is_fixed=True,
            length=12,
            width=6,
            height=4,
            weight=1200,
            area=72,
            building_period=35,
            earliest_start=0,
            latest_start=110,
            output_value=6000,
            placement_type="曲面",
            demand_node=110
        ),
        Segment(
            id=4,
            type="通用",
            is_fixed=False,
            length=7,
            width=3,
            height=2,
            weight=700,
            area=21,
            building_period=20,
            earliest_start=0,
            latest_start=80,
            output_value=3500,
            placement_type="平直",
            demand_node=80
        )
    ]
    # segments = [
    #     Segment(1, "定置", True, 10, 5, 3, 1000, 50, 30, 0, 100, 5000, "曲面", 100),
    #     Segment(2, "通用", False, 8, 4, 2, 800, 32, 25, 0, 90, 4000, "平直", 90),
    #     Segment(3, "定置", True, 12, 6, 4, 1200, 72, 35, 0, 110, 6000, "曲面", 110),
    #     Segment(4, "通用", False, 7, 3, 2, 700, 21, 20, 0, 80, 3500, "平直", 80),
    # ]

    # 设置组合分段
    segments[0].is_combined = True
    segments[0].combined_segments = [segments[0], segments[1]]
    segments[1].is_combined = True
    segments[1].combined_segments = [segments[0], segments[1]]

    # 设置成对分段
    segments[2].pair_segment = segments[3]
    segments[3].pair_segment = segments[2]

    # 创建胎位
    mold_positions = [
        MoldPosition(
            id=1,
            type="定置",
            placement_type="曲面",
            length=20,
            width=10,
            height=5,
            load_capacity=5000,
            area=200,
            labor_team=1,
            is_mobile=False
        ),
        MoldPosition(
            id=2,
            type="通用",
            placement_type="平直",
            length=15,
            width=8,
            height=4,
            load_capacity=4000,
            area=120,
            labor_team=2,
            is_mobile=False
        ),
        MoldPosition(
            id=3,
            type="定置",
            placement_type="曲面",
            length=20,
            width=10,
            height=5,
            load_capacity=5000,
            area=200,
            labor_team=3,
            is_mobile=False
        ),
        MoldPosition(
            id=4,
            type="通用",
            placement_type="平直",
            length=15,
            width=8,
            height=4,
            load_capacity=4000,
            area=120,
            labor_team=4,
            is_mobile=False
        )
    ]

    # 创建排程系统并运行排程
    scheduler = SchedulingSystem(segments, mold_positions)
    scheduler.schedule_segments()
    scheduler.display_results()
